//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace WorkflowApplicationControls
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Practices.Unity;
    using SR = Microsoft.Research.DataLayer;
    using WorkflowApplication.Interfaces;
    using Microsoft.Research.ScientificWorkflow.UIDesigner;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.Monitor;
    using System.ComponentModel;
    using System.Windows.Controls;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using System.Windows;
    using System.Globalization;

    /// <summary>
    /// The controller for the center pane of the workflow application.
    /// </summary>
    public class CenterPaneController : INotifyPropertyChanged
    {
        #region Private Data

        /// <summary>
        /// The Unity container instance used for dependency injection.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// The region manager representing the main window.
        /// </summary>
        private IRegionManager regionMgr;

        /// <summary>
        /// Index of the selected tab.
        /// </summary>
        private int selectedTabIndex;

        /// <summary>
        /// The current workflow model.
        /// </summary>
        private TridentWorkflowModel currentWorkflowModel;

        /// <summary>
        /// The current workflow id.
        /// </summary>
        private Guid workflowId;

        /// <summary>
        /// The presenter for the run window.
        /// </summary>
        private RunControlPresenter runPresenter;

        /// <summary>
        /// The outputs presenter.
        /// </summary>
        private WorkflowParametersPresentationModel outputsPresenter;

        /// <summary>
        /// The toolbar presenter.
        /// </summary>
        private ToolBarPresenter toolBarPresenter;

        /// <summary>
        /// The run control.
        /// </summary>
        private UserControl runControl;

        /// <summary>
        /// The output control.
        /// </summary>
        private UserControl outputControl;

        /// <summary>
        /// The registry service.
        /// </summary>
        private IRegistryService registryService;

        #endregion Private Data

        #region Events

        /// <summary>
        /// Request for can execute checks.
        /// </summary>
        public event EventHandler RequestCanExecuteCheck;

        #endregion Events

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="CenterPaneController"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public CenterPaneController(IUnityContainer container, CenterPane centerPane, IRegistryService service)
        {
            this.container = container;
            this.registryService = service;
            this.View = centerPane;
            this.View.DataContext = this;
        }

        #endregion Constructor

        #region Public Properties

        /// <summary>
        /// Gets or sets the workflow id.
        /// </summary>
        /// <value>The workflow id.</value>
        public Guid WorkflowId
        {
            get
            {
                return this.workflowId;
            }
            set
            {
                this.workflowId = value;
                this.CreateWorkflowModel();
                this.OnSelectedWorkflowChange();
            }
        }

        /// <summary>
        /// The model of the workflow to be displayed in the window.
        /// </summary>
        public TridentWorkflowModel CurrentWorkflowModel
        {
            get
            {
                return this.currentWorkflowModel;
            }
            set
            {
                this.currentWorkflowModel = value;
                this.RaisePropertyChanged("CurrentWorkflowModel");
            }
        }

        /// <summary>
        /// Gets or sets the view.
        /// </summary>
        /// <value>The view.</value>
        public CenterPane View { get; set; }

        /// <summary>
        /// Gets or sets the index of the selected tab.
        /// </summary>
        /// <value>The index of the selected tab.</value>
        public int SelectedTabIndex
        {
            get
            {
                return this.selectedTabIndex;
            }
            set
            {
                this.selectedTabIndex = value;
                this.RaisePropertyChanged("SelectedTabIndex");
            }
        }

        /// <summary>
        /// Gets or sets the output control.
        /// </summary>
        /// <value>The output control.</value>
        public UserControl OutputControl
        {
            get
            {
                return this.outputControl;
            }
            set
            {
                this.outputControl = value;
                this.RaisePropertyChanged("OutputControl");
            }
        }

        /// <summary>
        /// Gets or sets the run control.
        /// </summary>
        /// <value>The run control.</value>
        public UserControl RunControl
        {
            get
            {
                return this.runControl;
            }
            set
            {
                this.runControl = value;
                this.RaisePropertyChanged("RunControl");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating if the execution is currently in progress.
        /// </summary>
        public bool ExecutionMode { get; set; }

        /// <summary>
        /// Gets or sets the current executing job.
        /// </summary>
        /// <value>The current executing job.</value>
        public SR.Job CurrentExecutingJob { get; set; }

        #endregion Public Properties

        #region Public Methods

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        public void Initialize()
        {
            this.regionMgr = this.container.Resolve<IRegionManager>("MainWindow");
            this.runPresenter = this.container.Resolve<RunControlPresenter>();
            DataProductPresentationModel dataProdPresentationModel = new DataProductPresentationModel(this.container, new DataProductsView());
            this.container.RegisterInstance<DataProductPresentationModel>("DataProductsPresenter", dataProdPresentationModel);
            this.outputsPresenter = new WorkflowParametersPresentationModel(this.container, new WorkflowParametersView(), dataProdPresentationModel);
            this.container.RegisterInstance<WorkflowParametersPresentationModel>("OutputsPresenter", this.outputsPresenter);
            this.toolBarPresenter = this.container.Resolve<ToolBarPresenter>("ToolbarPresenter");
            this.toolBarPresenter.PlayCommand = new DelegateCommand<object>(p => this.PlayCommandExecute(), p => this.PlayCommandCanExecute());
            this.toolBarPresenter.PauseCommand = new DelegateCommand<object>(p => CenterPaneController.PauseCommandExecute(), p => CenterPaneController.PauseCommandCanExecute());
            this.toolBarPresenter.TerminateCommand = new DelegateCommand<object>(p => this.TerminateCommandExecute(), p => this.TerminateCommandCanExecute());
        }

        /// <summary>
        /// Terminates the current job.
        /// </summary>
        public void TerminateJob()
        {
            try
            {
                if (this.CurrentExecutingJob != null)
                {
                    SR.Job job = SR.Job.Load((this.CurrentExecutingJob as SR.IObject).ID,
                        this.registryService.CurrentConnection);
                    if (job != null)
                    {
                        if (job.Status == SR.JobStatus.Waiting)
                        {
                            job.Status = SR.JobStatus.Aborted;
                        }
                        else if (job.Status != SR.JobStatus.Completed || job.Status != SR.JobStatus.Aborted)
                        {
                            job.Status = SR.JobStatus.StopPending;
                        }
                        job.ErrorMessage = string.Format(CultureInfo.InvariantCulture, "{0} [{1} ({2})].", WorkflowApplicationResourceManager.GetString("JobTerminatedByUserMessage")
                                                , TridentAuthentication.LoggedUserInUserName, TridentAuthentication.LoggedUserRole.ToString());
                        job.Save();
                    }
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }
        }

        /// <summary>
        /// Clears the run window.
        /// </summary>
        public void ClearRunWindow()
        {
            this.RunControl = null;
            this.runPresenter.IsInitialized = false;
            this.CurrentWorkflowModel = null;
            this.RaiseCanExecute();
        }

        #endregion Public Methods

        #region Private Methods

        private void CreateWorkflowModel()
        {
            SR.Activity workflow = this.registryService.GetWorkflow(this.WorkflowId);
            ModelFactory modelFactory = new ModelFactory(null, null);
            this.CurrentWorkflowModel = new TridentWorkflowModel(workflow, modelFactory);
        }

        private void OnSelectedWorkflowChange()
        {
            this.runPresenter.CurrentWorkflowModel = this.CurrentWorkflowModel;
            this.SelectCenterPane(this.runPresenter);
            this.RaiseCanExecute();
        }

        private void SelectCenterPane(ICenterPanePresenter centerPane)
        {
            if (centerPane is RunControlPresenter)
            {
                if (!centerPane.IsInitialized)
                {
                    this.RunControl = centerPane.ContentView;
                    centerPane.IsInitialized = true;
                }

                this.SelectedTabIndex = 0;
            }
            else if (centerPane is WorkflowParametersPresentationModel)
            {
                if (!centerPane.IsInitialized)
                {
                    this.OutputControl = centerPane.ContentView;
                    centerPane.IsInitialized = true;
                }

                this.SelectedTabIndex = 1;
            }
        }

        private void RaiseCanExecute()
        {
            this.toolBarPresenter.PlayCommand.RaiseCanExecuteChanged();
            this.toolBarPresenter.PauseCommand.RaiseCanExecuteChanged();
            this.toolBarPresenter.TerminateCommand.RaiseCanExecuteChanged();
            if (this.RequestCanExecuteCheck != null)
            {
                this.RequestCanExecuteCheck.Invoke(this, new EventArgs());
            }
        }

        private bool PlayCommandCanExecute()
        {
            bool canExecute = false;
            if (this.CurrentWorkflowModel != null && !this.ExecutionMode)
            {
                canExecute = true;
            }

            return canExecute;
        }

        private void PlayCommandExecute()
        {
            try
            {
                if (this.CurrentWorkflowModel != null)
                {
                    SR.Activity workflow = this.registryService.GetWorkflow(this.CurrentWorkflowModel.Id);
                    if (workflow == null || workflow.IsDeleted)
                    {
                        TridentMessageBox.ShowTridentErrorMessageBox(
                            WorkflowApplicationResourceManager.GetString("WfDeleted"));
                        return;
                    }
                }

                SR.Job newJob = null;
                if (this.runPresenter.CreateJobCommand != null)
                {
                    JobWrapper jobWrapper = new JobWrapper();
                    this.runPresenter.CreateJobCommand.Execute(new object[] { jobWrapper });
                    newJob = jobWrapper.Job;
                }

                if (newJob != null)
                {
                    this.outputsPresenter.CurrentWorkflowModel = this.CurrentWorkflowModel;
                    this.SelectCenterPane(this.outputsPresenter);
                    this.StartJobMonitor(newJob, this.registryService.CurrentConnection);
                    this.CurrentExecutingJob = newJob;
                    this.ExecutionMode = true;
                    this.RaiseCanExecute();
                }
            }
            catch (TridentCustomException tridentException)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(tridentException);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
            }
        }

        private static bool PauseCommandCanExecute()
        {
            return false;
        }

        private static void PauseCommandExecute()
        {
        }

        private bool TerminateCommandCanExecute()
        {
            bool canExecute = false;
            if (this.CurrentWorkflowModel != null && this.ExecutionMode)
            {
                canExecute = true;
            }

            return canExecute;
        }

        private void TerminateCommandExecute()
        {
            if (MessageBoxResult.Yes == TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                WorkflowApplicationResourceManager.GetString("ConfirmTerminateMessage")))
            {
                this.TerminateJob();
            }
        }

        private void StartJobMonitor(SR.Job job, SR.Connection conn)
        {
            JobMonitorHelper jobMonitorHelper = new JobMonitorHelper();
            jobMonitorHelper.OnJobChanged += new EventHandler<JobStatusEventArgs>(this.OnJobChanged);
            jobMonitorHelper.StartJobMonitor(job, conn);
        }

        private void OnJobChanged(object sender, JobStatusEventArgs e)
        {
            if (e != null
                && e.ChangedJob != null
                && e.ItemChanged != JobItemChanged.None)
            {
                SR.Job modifiedJob = e.ChangedJob;
                JobItemChanged changedItem = e.ItemChanged;
                switch (changedItem)
                {
                    case JobItemChanged.None:
                        break;

                    case JobItemChanged.Status:
                        this.NotifyStatusChange(modifiedJob, modifiedJob.Status);
                        break;

                    case JobItemChanged.Outputs:
                        this.NotifyOutputsChange(modifiedJob);
                        break;

                    case JobItemChanged.StatusAndOutputs:
                        this.NotifyStatusAndOutputsChange(modifiedJob);
                        break;

                    case JobItemChanged.Timeout:
                        this.NotifyTimeout();
                        break;

                    case JobItemChanged.FatalError:
                        this.NotifyJobExecutionFatalError(modifiedJob);
                        break;
                }
            }
        }

        private void NotifyStatusChange(SR.Job modifiedJob, SR.JobStatus jobStatus)
        {
            if (modifiedJob != null)
            {
                string jobName = modifiedJob.Name;
                string errorMessage = modifiedJob.ErrorMessage;

                StringBuilder statusBuilder = new StringBuilder(WorkflowApplicationResourceManager.GetString("ExecStatus"));
                statusBuilder.Append(jobName);
                statusBuilder.Append(" - ");
                statusBuilder.Append(jobStatus.ToString());

                this.container.Resolve<IStatusMessageControl>().SetText(statusBuilder.ToString());

                if (jobStatus == SR.JobStatus.Aborted || jobStatus == SR.JobStatus.Completed)
                {
                    this.outputsPresenter.RefreshOutputs(modifiedJob);
                    this.ExecutionMode = false;
                    this.CurrentExecutingJob = null;
                    this.RaiseCanExecute();

                    if (!string.IsNullOrEmpty(errorMessage))
                    {
                        TridentMessageBox.ShowTridentErrorMessageBox(WorkflowApplicationResourceManager.GetString("ExecutionFailed", errorMessage));
                    }
                }
            }
        }

        private void NotifyOutputsChange(SR.Job modifiedJob)
        {
            if (modifiedJob != null)
            {
                this.outputsPresenter.RefreshOutputs(modifiedJob);
            }
        }

        private void NotifyStatusAndOutputsChange(SR.Job modifiedJob)
        {
            this.NotifyStatusChange(modifiedJob, modifiedJob.Status);
            this.NotifyOutputsChange(modifiedJob);
        }

        private void NotifyTimeout()
        {
            MessageBoxResult confirmResult =
                        TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                        WorkflowApplicationResourceManager.GetString("JobRetryMessage"));

            if (confirmResult == MessageBoxResult.No)
            {
                MessageBoxResult finalConfirmResult =
                    TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                    WorkflowApplicationResourceManager.GetString("ConfirmTerminateMessage"));

                if (finalConfirmResult == MessageBoxResult.Yes)
                {
                    this.TerminateJob();
                }
            }
        }

        private void NotifyJobExecutionFatalError(SR.Job modifiedJob)
        {
            this.NotifyStatusChange(modifiedJob, SR.JobStatus.Aborted);
        }

        #endregion Private Methods

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed.
        /// </summary>
        /// <param name="name">The name.</param>
        protected void RaisePropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }

    /// <summary>
    /// This class is used as a wrapper so that, when sent as reference across function calls,
    /// the Job can be changed or populated and the callee will know. This is used specifically 
    /// by commands as there can be no return values.
    /// </summary>
    public class JobWrapper
    {
        /// <summary>
        /// Gets or sets the job.
        /// </summary>
        /// <value>The job.</value>
        public SR.Job Job { get; set; }
    }
}
